package com.example.calendarapp;

import android.content.Context;

import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.*;
import android.util.Log;
import android.widget.Toast;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * this class checks if the data base is working
 * compares if an events repeat to prevent that
 * allows the users to create an event
 * if the sqlite data base give an error the program throws an exception
 * creates a data base if it is not already there
 * throws error if you make duplicate bases
 * add certain fields and events int the data base so we can input our proper information
 * checks if that fields are empty inorder to properly make that even in that field
 * allows for repeated events to occur for a set amount of time
 * properly sets up the date 
 * recalls the event by day hour and month to allow proper display
 * allows for the deletion of events
 * to delete the info it jumps in the sql down and up to remove it
 * logs errors that \occur
 * 
 * implements singleton pattern
 * @author Emilio
 *
 */

public class EventsDataSource {
	
	/**
	 * list of all the ints and strings used
	 * defines database name
	 * and helper
	 * also gives some fields in the data base for it you
	 * 
	 */
	
	protected static String TAG = "EventsDataSource";
	private SQLiteDatabase db;
	private EventDatabaseOpenHelper helperDb;
	private final Context mContext;
	private static final String[] allcolumnsEvent = {"_id", "name", "desc", "date"};
	private static final String[] allcolumnsCat = {"_id", "name", "color"};
	private static EventsDataSource instance;
	private static Map<Integer, EventCategory> map = new HashMap<Integer,EventCategory>();
	private static List<EventCategory> categories = new ArrayList<EventCategory>();
	
	
	/**
	 * creates a newdata base helper for ctx
	 * which stand for context
	 * return refence to ctx
	 * @param ctx
	 */
	
	private EventsDataSource(Context ctx)
	{
		helperDb = new EventDatabaseOpenHelper(ctx);
		this.mContext = ctx;
	}
	
	/**
	 * if the data base fails throw and exception
	 * if it passees creat the data base
	 * also catch to allow the error to be displayed an potentially findout the error
	 * display error in error log
	 * @return
	 * @throws SQLException
	 */
	
	public EventsDataSource createDatabase() throws SQLException
	{
		try
		{
			helperDb.createDataBase();
		}
		catch(IOException mIOException)
		{
			Log.e(TAG, mIOException.toString() + " Unable to create database");
			throw new Error("UnableToCreateDatabase");
		}
		return this;
	}
	
	/**
	 * try to open the data base
	 * makes bd into the database
	 * if you fail catch the exception and throw it
	 * return the throw if the program fials
	 * @return
	 * @throws SQLException
	 */
	
	public EventsDataSource open() throws SQLException
	{
			try
			{
				helperDb.openDataBase();
				helperDb.close();
				db = helperDb.getReadableDatabase();
				loadCats();
				}
			catch(SQLException mSQLException)
			{
				Log.e(TAG, "open >>" + mSQLException.toString());
				throw mSQLException;
			}
			return this;
	}
	
	/**
	 * forms the event date string
	 * much like several method this one works to create a string tells you where it repeats
	 * moves the monthcount to that location
	 * closes one it gets the int of that inital month
	 * @param year
	 * @param month
	 * @param date
	 * @param weekday
	 * @return
	 */
	
	public int getNumEvents(int year, int month, int date, int weekday)
	{
		String dateString = formatString(year, month+1, date);
		Cursor mCount = db.rawQuery("select count(*) from (select * from event where strftime('%Y-%m-%d', `date`) = '" + dateString + "'" +
				" union select * from repeat where day = " + weekday + ")", null);
		mCount.moveToFirst();
		int count = mCount.getInt(0);
		mCount.close();
		return count;
	}
	
	/**
	 * holds all our objects to set up the category 
	 * then we pump thos into the sql database
	 * if you can add thos categories then do it.
	 * then exit out of the function if not possible return false
	 * @param name
	 * @param year
	 * @param month
	 * @param day
	 * @param hour
	 * @param length
	 * @param cat
	 * @ppackage com.example.calendarapp;aram weekday
	 * @return
	 */
	
	public boolean add(String name, int year, int month, int day, int hour, int length, int category, int weekday)
	{
		String dateString = formatString(year, month, day);
		int end = hour+length;
		int cat = categories.get(category).getId();
		if(canAdd(hour, end, dateString, weekday))
		{
				db.execSQL("insert into event (name, date, hour, cat, hourEnd, day) values (" +
						"'" + name + "','" + dateString + "','" + hour + "','" + cat + "','" + end + "','" + weekday + "')");
				return true;
		}
		return false;
		
	}
	
	/**
	 * sipackage com.example.calendarapp;milar to the previous method except you add multiple repeated events
	 * no real changes asside from that
	 * @param name
	 * @param hour
	 * @param length
	 * @param cat
	 * @param weekday
	 * @return
	 */
	
	public boolean addRepeated(String name, int hour, int length, int cat, int weekday)
	{
		int end = hour+length;
		if(canAddRepeat(weekday, hour, hour+length))
		{
			
			db.execSQL("insert into repeat (name, hour, cat, hourEnd, day) values (" +
					"'" + name + "'," + hour + "," + cat + "," + end  + "," + weekday + ")");
			return true;
		}
		return false;
	}

	/**
	 * formats the string in order
	 * makes several place holder
	 * a bunch of if statements to allow the ints to all go through
	 * at the end places the needed events into your place holders
	 * then combind place holders and returns them
	 * @param year
	 * @param month
	 * @param day
	 * @return
	 */
	
	public String formatString(int year, int month, int day)
	{
		String dateString, monthString, yearString, dayString;
		if(day < 10)
		{
			dayString = "0" + Integer.toString(day);
		}
		else
		{
			dayString = Integer.toString(day);
		}
		if(month < 10)
		{
			monthString = "0" + Integer.toString(month);
		}
		else
		{
			monthString = Integer.toString(month);
		}
		yearString = String.valueOf(year);
		dateString = yearString + "-" + monthString + "-" + dayString;
		return dateString;
	}
	
	/**
	 * makes the categories from your strings
	 * makes list of string out of an array
	 * if data base is not available then call an error to the error log
	 * move the cursor where you move
	 * continues cycle thorugh it until you are finished
	 * close it and return the array
	 * @package com.example.calendarapp;return list<string> categories strings
	 */
	
	public List<String> getCategoriesStrings()
	{
		loadCats();
		List<String> cats = new ArrayList<String>();
		if(db == null) Log.e("EDS", "null");
		Cursor cursor = db.rawQuery("select * from cat", null);
		cursor.moveToFirst();
		
		while(!cursor.isAfterLast())
		{
			cats.add(cursor.getString(1));
			cursor.moveToNext();
		}
		cursor.close();
		
		return cats;

	}
	/**
	 * update a category in the database
	 * @param id
	 * @param name
	 * @param color
	 */
	public void updateCategory(int id, String name, int color)
	{
		String query = "update cat set name = '" + name + "', color = " + color + " where _id = " + id;
		db.execSQL(query);
	}
	
	/**
	 * create a category in the database
	 * @param name
	 * @param color
	 */
	
	public void addCategory(String name, int color)
	{
		String query = "insert into cat (name, color) values ('" + name +"',"+color+")" ;
		db.execSQL(query);
	}
	
	/**
	 * Delete a category from the database, sets events with that category to category 0 and reloads category info
	 * @param id
	 */
	
	public void deleteCategory(int id)
	{
		String query = "update event set cat = 1 where cat = " + id;
		db.execSQL(query);
		query = "delete from cat where _id = " + id;
		db.execSQL(query);
		loadCats();
	}
	
	/**
	 * package com.example.calendarapp;load the categories from the database, load a list of categories to get a category from a given position and
	 * get a map of catgeries to get category by id
	 */
	
	public void loadCats()
	{
		
		Cursor cursor = db.rawQuery("select * from cat", null);
		cursor.moveToFirst();
		categories.clear();
		map.clear();
		int pos = 0;
		while(!cursor.isAfterLast())
		{
			EventCategory cat = cursorToCat(cursor, pos);
			categories.add(cat);
			map.put(cat.getId(), cat);
			cursor.moveToNext();
			pos++;
			}
		cursor.close();
	}
	
	/**
	 * loads the categories and then returns the list that was generated 
	 */
	
	public List<EventCategory> getCategories()
	{
	
		loadCats();
		return categories;

	}
	
	/**
	 * closes the database
	 */
	public void close()
	{
		helperDb.close();
	}
	/**
	 * gets a list off all events and then returns
	 * crrat a new event list from an array
	 * get the location of your cursor at the data base query
	 * continue until done
	 * @return list<event>
	 */
	
	public List<Event> getAllEvents()
	{
		List<Event> events = new ArrayList<Event>();
		Cursor cursor = db.query("event", allcolumnsEvent, null, null, null, null, null);
		
		cursor.moveToFirst();
		while(!cursor.isAfterLast())
		{
			events.add(cursorToEvent(cursor));
			cursor.moveToNext();
		}
		cursor.close();
		return events;
	}
	
	/**
	 * checks if there are any conflicts when adding events
	 * 
	 * @param hourS
	 * @param hourE
	 * @param date
	 * @param weekday
	 * @return canAdd
	 */
	
	public boolean canAdd(int hourS, int hourE, String date, int weekday)
	{
		Cursor mCount = db.rawQuery("select count(*) from event where strftime('%Y-%m-%d', `date`) = '" + date + "' and " +
				"hour <= " + hourS + " and hourEnd > " + hourS, null);
		mCount.moveToFirst();
		int count = mCount.getInt(0);
		mCount.close();
		if(count != 0)
		{
			Log.e("EDS", "caught bad add1");
			Log.e("EDS", date);
			return false;
		}
		mCount = db.rawQuery("select count(*) from event where strftime('%Y-%m-%d', `date`) = '" + date + "' and " +
				"hour < " + hourE + " and hourEnd >= " + hourE, null);
		mCount.moveToFirst();
		count = mCount.getInt(0);
		if(count != 0)
		{
			Log.e("EDS", "caught bad add2");
			return false;
		}	
		mCount = db.rawQuery("select count(*) from event where strftime('%Y-%m-%d', `date`) = '" + date + "' and " +
				"hour >= " + hourS + " and hourEnd <= " + hourE, null);
		mCount.moveToFirst();
		count = mCount.getInt(0);
		if(count != 0)
		{
			Log.e("EDS", "caught bad add3");
			return false;
		}
		
		mCount = db.rawQuery("select count(*) from repeat where day = " + weekday + " and " +
				"hour <= " + hourS + " and hourEnd > " + hourS, null);
		mCount.moveToFirst();
		count = mCount.getInt(0);
		mCount.close();
		if(count != 0)
		{
			Log.e("EDS", "caught bad add4");
			return false;
		}
		mCount = db.rawQuery("select count(*) from repeat where day = " + weekday + " and " +
				"hour < " + hourE + " and hourEnd >= " + hourE, null);
		mCount.moveToFirst();
		count = mCount.getInt(0);
		if(count != 0)
		{
			Log.e("EDS", "caught bad add5");
			return false;
		}	
		mCount = db.rawQuery("select count(*) from repeat where day = " + weekday + " and " +
				"hour >= " + hourS + " and hourEnd <= " + hourE, null);
		mCount.moveToFirst();
		count = mCount.getInt(0);
		if(count != 0)
		{
			Log.e("EDS", "caught bad add6");
			return false;
		}
		
		mCount.close();
		
		return true;
	}
	
	/**
	 * checks if there are any conflicts when adding repeated events, but has to check every event with matching day instead of with matching date
	 * 
	 * @param day
	 * @param hourS
	 * @param hourE
	 * @return canAdd
	 */
	
	public boolean canAddRepeat(int day, int hourS, int hourE)
	{
		Cursor mCount = db.rawQuery("select count(*) from event where day = " + day + " and " +
				"hour <= " + hourS + " and hourEnd > " + hourS, null);
		mCount.moveToFirst();
		int count = mCount.getInt(0);
		mCount.close();
		if(count != 0)
		{
			Log.e("EDS", "caught bad add1");
			return false;
		}
		mCount = db.rawQuery("select count(*) from event where day = " + day + " and " +
				"hour < " + hourE + " and hourEnd >= " + hourE, null);
		mCount.moveToFirst();
		count = mCount.getInt(0);
		if(count != 0)
		{
			Log.e("EDS", "caught bad add2");
			return false;
		}	
		mCount = db.rawQuery("select count(*) from event where day = '" + day + "' and " +
				"hour >= " + hourS + " and hourEnd <= " + hourE, null);
		mCount.moveToFirst();
		count = mCount.getInt(0);
		if(count != 0)
		{
			Log.e("EDS", "caught bad add3");
			return false;
		}
		
		mCount = db.rawQuery("select count(*) from repeat where day = " + day + " and " +
				"hour <= " + hourS + " and hourEnd > " + hourS, null);
		mCount.moveToFirst();
		count = mCount.getInt(0);
		mCount.close();
		if(count != 0)
		{
			Log.e("EDS", "caught bad add4");
			return false;
		}
		mCount = db.rawQuery("select count(*) from repeat where day = " + day + " and " +
				"hour < " + hourE + " and hourEnd >= " + hourE, null);
		mCount.moveToFirst();
		count = mCount.getInt(0);
		if(count != 0)
		{
			Log.e("EDS", "caught bad add5");
			return false;
		}	
		mCount = db.rawQuery("select count(*) from repeat where day = " + day + " and " +
				"hour >= " + hourS + " and hourEnd <= " + hourE, null);
		mCount.moveToFirst();
		count = mCount.getInt(0);
		if(count != 0)
		{
			Log.e("EDS", "caught bad add6");
			return false;
		}
		
		mCount.close();
		
		return true;
	}
	
	/**
	 * creates a new event
	 * @param cur
	 * @return Event
	 */
	
	public Event cursorToEvent(Cursor cur)
	{
		Event e = new Event();
		e.setName(cur.getString(1));
		e.setDate(cur.getString(2));
		e.setHourS(Integer.parseInt(cur.getString(3)));
		e.setHourE(Integer.parseInt(cur.getString(5)));
		e.setId(cur.getInt(0));
		e.setColor(map.get(cur.getInt(4)).getColorCode());
		e.setCatPos(map.get(cur.getInt(4)).getPosition());
		Log.e("EDS", Integer.toString(e.getCatPos()));
		return e;
	}
	
	/**
	 * creates a new category
	 * @param cur
	 * @return EventCategory
	 */
	
	public EventCategory cursorToCat(Cursor cur, int pos)
	{
		EventCategory c = new EventCategory(cur.getString(1), cur.getInt(2), cur.getInt(0));
		c.setPosition(pos);
		return c;
	}
	
	/**
	 * Singleton pattern method, returns static instance if there is one, else creates new object
	 * @param Context
	 * @return EventsDataSource
	 */
	
	public static EventsDataSource getInstance(Context ctx)
	{
		if(instance == null)
			instance = new EventsDataSource(ctx);
		return instance;
	}
	
	/**
	 * gets the time of location of time
	 * the cursor will get the time and hour
	 * then it gets the time ends and returns it
	 * if your cur has already something in it close your cur and return none
	 * if not coninue once your if are done
	 * and create event e from cur
	 * @param hour
	 * @param dateString
	 * @param day
	 * @return Event
	 */

	public Event getHourEvent(int hour, String dateString, int day)
	{
		Cursor cur;
		cur = db.rawQuery("select * from event where strftime('%Y-%m-%d', `date`) = '"+ dateString+"' and hour <= " + hour + " and hourEnd > " + hour, null);
		if(cur.getCount() == 0)
		{
			cur = db.rawQuery("select * from repeat where day = " + day + " and hour <= " + hour + " and hourEnd > " + hour , null);
			if(cur.getCount() != 0)
			{
				cur.moveToFirst();
				Event e = curToRepeat(cur);
				cur.close();
				return e;
			}
			else
				cur.close();
				return null;
		}
		
		cur.moveToFirst();
		Event e =  cursorToEvent(cur);
		cur.close();
		return e;
	}
	
	/**
	 *  gives cur the location of the database location with the time of the
	 *  moves the new event to the front
	 *  sends it to the front once you are done
	 *  closes the area once you finish.
	 *  sends a string to tell you location of the date and time
	 * @param date
	 * @param weekday
	 * @return ArrayList<Event>
	 */

	public ArrayList<Event> getDayEvents(String date, int weekday)
	{
		ArrayList<Event> events = new ArrayList<Event>();
		
		Cursor cur = db.rawQuery("select * from event where strftime('%Y-%m-%d', `date`) = '" + date + "'" +
				" union select * from repeat where day = " +weekday+ " order by hour asc", null);
		cur.moveToFirst();
		while(!cur.isAfterLast())
		{
			
			if(cur.getString(2)!=null)events.add(cursorToEvent(cur));
			else events.add(curToRepeat(cur));
			cur.moveToNext();
		}
		cur.close();
		return events;
				
	}
	
	/**
	 * gets the event by the id
	 * if the event repeat shove it down the bottom fuction
	 * Essentially creates an event at the location then prevents that location from getting
	 * another event there 
	 * @param id
	 * @param repeat
	 * @return Event
	 */
	
	public Event getEventsById(int id, boolean repeat)
	{
		if(repeat) return getRepeatById(id);
		Cursor cur = db.rawQuery("select * from event where _id = " + id, null);
		cur.moveToFirst();
		Event e = cursorToEvent(cur);
		cur.close();
		return e;
	}

	public Event getRepeatById(int id)
	{
		Cursor cur = db.rawQuery("select * from repeat where _id = " + id, null);
		cur.moveToFirst();
		Event e = curToRepeat(cur);
		cur.close();
		return e;
		
	}
	
	/**
	 * is sent from previous method
	 * creates a repeated event by id
	 * closes the current location where we repeated placed the event
	 * work similarlly to the top one
	 * @param id
	 * @return Event
	 */
	
	public EventCategory getCategoryByPos(int pos)
	{
		return categories.get(pos);
	}
	

	/**
	 * removes the event if it is a repeated event
	 * @param id
	 */
	public void removeRepeatId(int id)
	{
		db.execSQL("delete from repeat where _id = " + id);
	}
	/**
	 * removes the event if it is a singular event
	 * @param id
	 */
	public void removeById(int id, boolean repeated)
	{
		if(repeated) removeRepeatId(id);
		else db.execSQL("delete from event where _id = " + id);
	}
	
	/**
	 * set the event up to be repeated
	 * uses the set up fields and moves it down the required amount while using event e to store the repeated event
	 * uses cursor cur to tell location
	 * @param cur
	 * @return Event
	 */

	public Event curToRepeat(Cursor cur)
	{
		Event e = new Event();
		e.setId(cur.getInt(0));
		e.setName(cur.getString(1));
		e.setHourS(cur.getInt(3));
		e.setCat(cur.getInt(4));
		e.setHourE(cur.getInt(5));
		e.setRepeatDay(cur.getInt(6));
		return e;
		
	}
}
	